using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace LastFMPPC
{
	class SocketConnection
	{
		private Socket s = null;
		private string server;
		private int port;

		private Mutex mutex;

		/// <summary>
		/// Callback for receiving data in case of async receive.
		/// </summary>
		/// <param name="buffer">Buffer containing data.</param>
		/// <param name="length">Number of bytes received.</param>
		public delegate void SocketConnectionCB(int length);
		private event SocketConnectionCB socketConnectionReceiveEvent = null;
		private void OnSocketConnectionReceive(IAsyncResult iar)
		{
			mutex.WaitOne();
			try
			{
				if (s != null)
				{
					int receiveLength = s.EndReceive(iar);
					if (socketConnectionReceiveEvent != null)
					{
						socketConnectionReceiveEvent(receiveLength);
					}
				}
			}
			catch (Exception exp)
			{
				if (socketConnectionReceiveEvent != null)
				{
					// informing the above layer that error happend.
					socketConnectionReceiveEvent(-1);
				}
			}
			finally
			{
				mutex.ReleaseMutex();
			}
		}
		
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="server">Server name.</param>
		/// <param name="port">Port number.</param>
		public SocketConnection(string server, int port)
		{
			try
			{
				this.server = server;
				this.port = port;

				mutex = new Mutex();
			}
			catch (Exception exp)
			{
				throw new LastException("SocketConnection.SocketConnection: " + exp.Message);
			}
		}
		
		/// <summary>
		/// Connects the connection.
		/// </summary>
		public void Connect()
		{
			mutex.WaitOne();
			try
			{
				if (s == null)
				{
					IPHostEntry hostEntry = null;

					// Get host related information.
					hostEntry = Dns.GetHostEntry(server);

					// Loop through the AddressList to obtain the supported AddressFamily. This is to avoid
					// an exception that occurs when the host IP Address is not compatible with the address family
					// (typical in the IPv6 case).
					foreach (IPAddress address in hostEntry.AddressList)
					{
						IPEndPoint ipe = new IPEndPoint(address, port);
						Socket tempSocket = new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

						tempSocket.Connect(ipe);

						if (tempSocket.Connected)
						{
							s = tempSocket;
							break;
						}
						else
						{
							continue;
						}
					}
				}
			}
			catch (Exception exp)
			{
				throw new LastException("SocketConnection.Connect: " + exp.Message);
			}
			finally
			{
				mutex.ReleaseMutex();
			}
		}

		/// <summary>
		/// Closes the connection.
		/// </summary>
		public void Close()
		{
			mutex.WaitOne();
			try
			{
				if (s != null) s.Close();
				s = null;
			}
			catch (Exception exp)
			{
				throw new LastException("SocketConnection.Close: " + exp.Message);
			}
			finally
			{
				mutex.ReleaseMutex();
			}
		}

		/// <summary>
		/// Reads a single line into a string.
		/// </summary>
		/// <returns>The line.</returns>
		public string ReadLine()
		{
			string line = "";

			mutex.WaitOne();
			try
			{
				if (s != null)
				{
					byte[] buffer = new byte[1];
					char c;
					while (true)
					{
						if (s.Receive(buffer, 1, SocketFlags.None) == 0) break;
						c = Convert.ToChar(buffer[0]);
						if (c == '\n') break;
						if (c != '\r') line += c;
					}
				}
			}
			catch (Exception exp)
			{
				throw new LastException("SocketConnection.ReadLine: " + exp.Message);
			}
			finally
			{
				mutex.ReleaseMutex();
			}

			return line;
		}
		
		/// <summary>
		/// Reads the 'size' amount of bytes into the buffer from the offset position.
		/// </summary>
		/// <param name="buffer">Buffer to read into.</param>
		/// <param name="size">Number of bytes to be read.</param>
		/// <param name="offset">Start offset in the buffer.</param>
		/// <returns>Number of bytes read.</returns>
		public int Read(byte[] buffer, int size, int offset)
		{
			int readSize = 0;

			mutex.WaitOne();
			try
			{
				readSize = s.Receive(buffer, offset, size, SocketFlags.None);
			}
			catch (Exception exp)
			{
				throw new LastException("SocketConnection.Read: " + exp.Message);
			}
			finally
			{
				mutex.ReleaseMutex();
			}

			return readSize;
		}
		
		/// <summary>
		/// Sends a string.
		/// </summary>
		/// <param name="data">The string to be sent.</param>
		public void Send(string data)
		{
			mutex.WaitOne();
			try
			{
				s.Send(Encoding.UTF8.GetBytes(data));
			}
			catch (Exception exp)
			{
				throw new LastException("SocketConnection.Send: " + exp.Message);
			}
			finally
			{
				mutex.ReleaseMutex();
			}
		}
		
		/// <summary>
		/// Sends raw data.
		/// </summary>
		/// <param name="data">The data to be sent.</param>
		public void Send(byte[] data)
		{
			mutex.WaitOne();
			try
			{
				s.Send(data);
			}
			catch (Exception exp)
			{
				throw new LastException("SocketConnection.Send: " + exp.Message);
			}
			finally
			{
				mutex.ReleaseMutex();
			}
		}

		/// <summary>
		/// Sets up the connection as an async reveiving socket.
		/// </summary>
		/// <param name="ReceiveCB">Callback</param>
		/// <param name="buffer">Buffer where the data will be received to.</param>
		public void BeginReceive(SocketConnectionCB receiveCB, byte[] buffer, int offset, int length)
		{
			mutex.WaitOne();
			try
			{
				socketConnectionReceiveEvent = receiveCB;
				s.BeginReceive(buffer, offset, length, SocketFlags.None, OnSocketConnectionReceive, null);
			}
			catch (Exception exp)
			{
				throw new LastException("SocketConnection.BeginReceive: " + exp.Message);
			}
			finally
			{
				mutex.ReleaseMutex();
			}
		}
	}
}
